/****************************************************************************

 Copyright (C) 2009 Jean Pierre Charalambos. All rights reserved.

 Jean Pierre Charalambos (http://dis.unal.edu.co/profesores/pierre/) is an
 Affiliate Associate Professor of the Faculty of Engineering at National
 University of Colombia (http://www.unal.edu.co/).

 This file is part of maku version 0.1.

 http://code.google.com/p/maku/
 email: jpcharalambosh@unal.edu.co

 This file may be used under the terms of the GNU General Public License 
 versions 2.0 or 3.0 as published by the Free Software Foundation and
 appearing in the COPYING file included in the packaging of this file.

 This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

*****************************************************************************/

#ifndef SCENET_H
#define SCENET_H

//scenebase must be the first included file, so that glew is included before gl
#include "scenebase.h"
#include "simplifiert.h"

//utility classes
#include "boundingboxt.h"
#ifdef WITH_CGAL
 #include "boundingspheret.h"
#else
 #include "boundingsphere.h"      
#endif

#include <OpenMesh/Core/IO/MeshIO.hh>
#include <OpenMesh/Core/IO/Options.hh>
#include <OpenMesh/Core/Utils/GenProg.hh>
#include <OpenMesh/Core/Utils/color_cast.hh>
#include <OpenMesh/Core/Mesh/Attributes.hh>
#include <OpenMesh/Tools/Utils/StripifierT.hh>
#include <OpenMesh/Tools/Utils/Timer.hh>
#include <OpenMesh/Tools/Utils/getopt.h>
#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>

using namespace OpenMesh;
using namespace std;

template <typename M>
class SceneT : public SceneBase {

public:

  typedef M                             Mesh;
  typedef OpenMesh::StripifierT<Mesh>   Stripifier;
  typedef SimplifierT<Mesh>             Simplifier;

  typedef BoundingBoxT<Mesh>            BBox;
  #ifdef WITH_CGAL
   typedef BoundingSphereT<Mesh>        BSphere;
  #else
   typedef BoundingSphere               BSphere;
  #endif
  

  SceneT() : SceneBase(), strips(mesh), texId(0), texMode(GL_MODULATE) {
    decOpt = new DecimaterOptions();
  }
  ~SceneT() {}

  OpenMesh::IO::Options& getReadOptions() { return rOpt; }
  const OpenMesh::IO::Options& getReadOptions() const { return rOpt; }
  void setReadOptions(const OpenMesh::IO::Options& opts) { rOpt = opts; }

  OpenMesh::IO::Options& getWriteOptions() { return wOpt; }
  const OpenMesh::IO::Options& getWriteOptions() const { return wOpt; }
  void setWriteOptions(const OpenMesh::IO::Options& opts) { wOpt = opts; }
  
  void setBoundingBox() { if (mesh.n_faces()>=1) bBox.setFromMesh(mesh); }
  const BBox & getBoundingBox() const { return bBox;};
  void setBoundingSphere() {
    if (mesh.n_faces()>=1)
      #ifdef WITH_CGAL
       bSphere.setTightBoundingSphere(mesh);
      #else
       bSphere.set( getBoundingBox().getBBMin(), getBoundingBox().getBBMax() );
      #endif  
  }
  const BSphere & getBoundingSphere() const { return bSphere;};

  //wrapper functions for the bounding sphere and bounding box objects:
  const Vec3f& getBBMin() const { return bBox.getBBMin(); }
  const Vec3f& getBBMax() const { return bBox.getBBMax(); }

  const float& getSphereRadius() const { return bSphere.getRadius(); }
  const Vec3f& getSphereCenter() const { return bSphere.getCenter(); }

  bool openModel(const QString & fname, OpenMesh::IO::Options opt) { rOpt = opt; return openModel(fname.toStdString()); }
  
  // current behaviour resets read options when openning a model
  bool openModel(const QString & fname) {
    OpenMesh::IO::Options opt;
    rOpt = opt;
    return openModel(fname.toStdString());
  }

  bool saveModel(const QString & fname, OpenMesh::IO::Options opt) { wOpt = opt;  return saveModel(fname.toStdString()); }
  
  // current behaviour resets write options when saving a model
  bool saveModel(const QString & fname) {
    OpenMesh::IO::Options opt;
    wOpt = opt;;
    return saveModel(fname.toStdString()); 
  }
  
  bool loadCube();

protected slots:

  virtual bool openTexture( const QString &);
  virtual void detachTexture() {
    if ( texId > 0 ) {
      glDeleteTextures(1, &texId);
    }
  }
  virtual bool simplifyModel();
  virtual void drawMesh() const;
  virtual void points(bool enabled) {
    if (enabled) {
      drawMode = Points;
      if (!mesh.has_vertex_colors() ) {
        pointColorDialogAct->setEnabled(true);
      }
      emit changed();
    }
    else {
      pointColorDialogAct->setEnabled(false);
    }
  };

protected:

  //virtual bool openModel(const string &, OpenMesh::IO::Options);
  virtual bool openModel(const string &);
  virtual bool saveModel(const string & fname) { return IO::write_mesh(mesh, fname, wOpt ); }
  virtual void drawOpenMesh(DrawMode) const;
  //funciones utilitarias
  void glVertex( const typename Mesh::VertexHandle _vh ) { glVertex3fv( &mesh.point( _vh )[0] ); }
  void glVertex( const typename Mesh::Point& _p ){ glVertex3fv( &_p[0] ); }
  void glNormal( const typename Mesh::VertexHandle _vh ) { glNormal3fv( &mesh.normal( _vh )[0] ); }
  void glTexCoord( const typename Mesh::VertexHandle _vh ) { glTexCoord2fv( &mesh.texcoord(_vh)[0] ); }
  void glColor( const typename Mesh::VertexHandle _vh ) { glColor3ubv( &mesh.color(_vh)[0] ); }
  // face properties
  void glNormal( const typename Mesh::FaceHandle _fh ) { glNormal3fv( &mesh.normal( _fh )[0] ); }
  void glColor( const typename Mesh::FaceHandle _fh ) const { glColor3ubv( &mesh.color(_fh)[0] ); }
  void glMaterial( const typename Mesh::FaceHandle _fh, int _f=GL_FRONT_AND_BACK, int _m=GL_DIFFUSE ) const {
    OpenMesh::Vec3f c=OpenMesh::color_cast<OpenMesh::Vec3f>(mesh.color(_fh));
    OpenMesh::Vec4f m( c[0], c[1], c[2], 1.0f );
    glMaterialfv(_f, _m, &m[0]);
  }

private:
  DecimaterOptions     * decOpt;
  Mesh                 mesh;
  BBox                 bBox;
  BSphere              bSphere;
  Stripifier           strips;
  OpenMesh::IO::Options  wOpt, rOpt; // mesh file contained texcoords?
  bool setTexture( QImage& );
  char *fn;
  GLuint                 texId;
  GLint                  texMode;
  void requestProperties() {
    mesh.request_face_normals();
    mesh.request_face_colors();
    mesh.request_vertex_normals();
    mesh.request_vertex_colors();
    mesh.request_vertex_texcoords2D();
  }
  void releaseProperties() {
    mesh.release_face_normals();
    mesh.release_face_colors();
    mesh.release_vertex_normals();
    mesh.release_vertex_colors();
    mesh.release_vertex_texcoords2D();
  }
  void setState() {
    detachTexture();
    decimateAct->setEnabled(true);
    drawModesActs->setEnabled(true);
    coloredVerticesAct->setEnabled(false);
    solidColoredFacesAct->setEnabled(false);
    smoothColoredFacesAct->setEnabled(false);
    openTexAct->setVisible(true);
    openTexAct->setEnabled(false);
    detachTexAct->setVisible(false);
    detachTexAct->setEnabled(false);
    // update face and vertex normals
    if ( ! rOpt.check( IO::Options::FaceNormal ) )
      mesh.update_face_normals();
    else
      std::cout << "File provides face normals\n";

    if ( ! rOpt.check( IO::Options::VertexNormal ) )
      mesh.update_vertex_normals();
    else
      std::cout << "File provides vertex normals\n";

    // check for possible color information
    if ( rOpt.check( IO::Options::VertexColor ) ) {
      std::cout << "File provides vertex colors\n";
      coloredVerticesAct->setEnabled(true);
    }
    else {
      mesh.release_vertex_colors();
    }

    if ( rOpt.check( IO::Options::FaceColor ) ) {
      std::cout << "File provides face colors\n";
      solidColoredFacesAct->setEnabled(true);
      smoothColoredFacesAct->setEnabled(true);
    }
    else {
      mesh.release_face_colors();
    }

    if ( rOpt.check( IO::Options::VertexTexCoord ) ) {
      std::cout << "File provides texture coordinates\n";
      openTexAct->setEnabled(true);
    }
    else {
      mesh.release_vertex_texcoords2D();
    }

    //Set Bounding Box:
    setBoundingBox();
    //Set Bounding Sphere:
    setBoundingSphere();

    // info
    std::clog << mesh.n_vertices() << " vertices, "
	      << mesh.n_edges()    << " edge, "
	      << mesh.n_faces()    << " faces\n";
    //
    std::clog << "Computing strips.." << std::flush;
    OpenMesh::Utils::Timer t;
    t.start();
    strips.clear();
    strips.stripify();
    t.stop();
    std::clog << "done [" << strips.n_strips() 
              << " strips created in " << t.as_string() << "]\n";

    // loading done
    drawMode = StripArrays;
    stripArraysAct->setChecked(true);//no emite la señal
    emit sceneChanged();//calls updateGL in connected viewers
  }
  //Mesh loadCube() {
  M createCube() {
    typedef typename M::VertexHandle VertexHandle;
    typedef typename M::Point Point;

    typename M::VertexHandle vhandle[8];

    //M cube;
    Mesh cube;

    //hack to make it compatible with default mesh
    cube.request_face_normals();
    cube.request_face_colors();
    cube.request_vertex_normals();
    cube.request_vertex_colors();
    cube.request_vertex_texcoords2D();

    vhandle[0] = cube.add_vertex(Point(-1, -1,  1));
    vhandle[1] = cube.add_vertex(Point( 1, -1,  1));
    vhandle[2] = cube.add_vertex(Point( 1,  1,  1));
    vhandle[3] = cube.add_vertex(Point(-1,  1,  1));
    vhandle[4] = cube.add_vertex(Point(-1, -1, -1));
    vhandle[5] = cube.add_vertex(Point( 1, -1, -1));
    vhandle[6] = cube.add_vertex(Point( 1,  1, -1));
    vhandle[7] = cube.add_vertex(Point(-1,  1, -1));

    // generate (quadrilateral) faces

    std::vector<VertexHandle>  face_vhandles;

    face_vhandles.clear();
    face_vhandles.push_back(vhandle[0]);
    face_vhandles.push_back(vhandle[1]);
    face_vhandles.push_back(vhandle[2]);
    face_vhandles.push_back(vhandle[3]);
    cube.add_face(face_vhandles);

    face_vhandles.clear();
    face_vhandles.push_back(vhandle[7]);
    face_vhandles.push_back(vhandle[6]);
    face_vhandles.push_back(vhandle[5]);
    face_vhandles.push_back(vhandle[4]);
    cube.add_face(face_vhandles);

    face_vhandles.clear();
    face_vhandles.push_back(vhandle[1]);
    face_vhandles.push_back(vhandle[0]);
    face_vhandles.push_back(vhandle[4]);
    face_vhandles.push_back(vhandle[5]);
    cube.add_face(face_vhandles);

    face_vhandles.clear();
    face_vhandles.push_back(vhandle[2]);
    face_vhandles.push_back(vhandle[1]);
    face_vhandles.push_back(vhandle[5]);
    face_vhandles.push_back(vhandle[6]);
    cube.add_face(face_vhandles);

    face_vhandles.clear();
    face_vhandles.push_back(vhandle[3]);
    face_vhandles.push_back(vhandle[2]);
    face_vhandles.push_back(vhandle[6]);
    face_vhandles.push_back(vhandle[7]);
    cube.add_face(face_vhandles);

    face_vhandles.clear();
    face_vhandles.push_back(vhandle[0]);
    face_vhandles.push_back(vhandle[3]);
    face_vhandles.push_back(vhandle[7]);
    face_vhandles.push_back(vhandle[4]);
    cube.add_face(face_vhandles);

    return cube;
  }
};

//=============================================================================
#if defined(OM_INCLUDE_TEMPLATES) && !defined(OPENMESHAPPS_SCENE_CPP)
#  define OPENMESH_SCENE_TEMPLATES
#  include "scenet.cpp"
#endif
//=============================================================================
#endif
